/**
 * Geoway矢量瓦片图层Provider
 */
(function (Cartesian2,
           Cartesian3,
           Cartographic,
           combine,
           Credit,
           defaultValue,
           defined,
           defineProperties,
           DeveloperError,
           Event,
           GeographicProjection,
           isArray,
           CesiumMath,
           Rectangle,
           Resource,
           WebMercatorTilingScheme,
           GeographicTilingScheme,
           when,
           ImageryProvider,
           Math) {

  'use strict';

  var templateRegex = /{[^}]+}/g;

  var tags = {
    'styleId': styleIdTag,
    'x': xTag,
    'y': yTag,
    'z': zTag,
    's': sTag,
    'reverseX': reverseXTag,
    'reverseY': reverseYTag,
    'reverseZ': reverseZTag,
    'westDegrees': westDegreesTag,
    'southDegrees': southDegreesTag,
    'eastDegrees': eastDegreesTag,
    'northDegrees': northDegreesTag,
    'westProjected': westProjectedTag,
    'southProjected': southProjectedTag,
    'eastProjected': eastProjectedTag,
    'northProjected': northProjectedTag,
    'width': widthTag,
    'height': heightTag
  };

  var pickFeaturesTags = combine(tags, {
    'i': iTag,
    'j': jTag,
    'reverseI': reverseITag,
    'reverseJ': reverseJTag,
    'longitudeDegrees': longitudeDegreesTag,
    'latitudeDegrees': latitudeDegreesTag,
    'longitudeProjected': longitudeProjectedTag,
    'latitudeProjected': latitudeProjectedTag,
    'format': formatTag
  });


  function VTileImageryProvider(options) {
    if (!defined(options)) {
      throw new DeveloperError('options is required.');
    }
    if (!when.isPromise(options) && !defined(options.url)) {
      throw new DeveloperError('options is required.');
    }

    this._errorEvent = new Event();

    this._resource = undefined;
    this._urlSchemeZeroPadding = undefined;
    this._pickFeaturesResource = undefined;
    this._tileWidth = undefined;
    this._tileHeight = undefined;
    this._maximumLevel = undefined;
    this._minimumLevel = undefined;
    this._tilingScheme = undefined;
    this._rectangle = undefined;
    this._tileDiscardPolicy = undefined;
    this._credit = undefined;
    this._hasAlphaChannel = undefined;
    this._readyPromise = undefined;
    this._tags = undefined;
    this._pickFeaturesTags = undefined;

    this._isLabel = false; //是否是注记层
    this._mapUrl = undefined;
    this._labelUrl = undefined;
    this._legendUrl = undefined;
    this._pickUrl = undefined;
    this._serverHost = undefined;
    this._serverName = undefined;

    this._control = undefined; //过滤控制

    this._labelProvider = undefined;
    this._hightlightProvider = undefined;

    /**
     * Gets or sets a value indicating whether feature picking is enabled.  If true, {@link VTileImageryProvider#pickFeatures} will
     * request the <code>options.pickFeaturesUrl</code> and attempt to interpret the features included in the response.  If false,
     * {@link VTileImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
     * features) without communicating with the server.  Set this property to false if you know your data
     * source does not support picking features or if you don't want this provider's features to be pickable.
     * @type {Boolean}
     * @default true
     */
    this.enablePickFeatures = false;

    this.enableRequestLabel = false;

    this.reinitialize(options);
  }

  defineProperties(VTileImageryProvider.prototype, {
    /**
     * Gets the URL template to use to request tiles.  It has the following keywords:
     * <ul>
     *  <li> <code>{z}</code>: The level of the tile in the tiling scheme.  Level zero is the root of the quadtree pyramid.</li>
     *  <li> <code>{x}</code>: The tile X coordinate in the tiling scheme, where 0 is the Westernmost tile.</li>
     *  <li> <code>{y}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Northernmost tile.</li>
     *  <li> <code>{s}</code>: One of the available subdomains, used to overcome browser limits on the number of simultaneous requests per host.</li>
     *  <li> <code>{reverseX}</code>: The tile X coordinate in the tiling scheme, where 0 is the Easternmost tile.</li>
     *  <li> <code>{reverseY}</code>: The tile Y coordinate in the tiling scheme, where 0 is the Southernmost tile.</li>
     *  <li> <code>{reverseZ}</code>: The level of the tile in the tiling scheme, where level zero is the maximum level of the quadtree pyramid.  In order to use reverseZ, maximumLevel must be defined.</li>
     *  <li> <code>{westDegrees}</code>: The Western edge of the tile in geodetic degrees.</li>
     *  <li> <code>{southDegrees}</code>: The Southern edge of the tile in geodetic degrees.</li>
     *  <li> <code>{eastDegrees}</code>: The Eastern edge of the tile in geodetic degrees.</li>
     *  <li> <code>{northDegrees}</code>: The Northern edge of the tile in geodetic degrees.</li>
     *  <li> <code>{westProjected}</code>: The Western edge of the tile in projected coordinates of the tiling scheme.</li>
     *  <li> <code>{southProjected}</code>: The Southern edge of the tile in projected coordinates of the tiling scheme.</li>
     *  <li> <code>{eastProjected}</code>: The Eastern edge of the tile in projected coordinates of the tiling scheme.</li>
     *  <li> <code>{northProjected}</code>: The Northern edge of the tile in projected coordinates of the tiling scheme.</li>
     *  <li> <code>{width}</code>: The width of each tile in pixels.</li>
     *  <li> <code>{height}</code>: The height of each tile in pixels.</li>
     * </ul>
     * @memberof VTileImageryProvider.prototype
     * @type {String}
     * @readonly
     */
    url: {
      get: function () {
        return this._resource.url;
      }
    },

    /**
     * Gets the URL scheme zero padding for each tile coordinate. The format is '000' where each coordinate will be padded on
     * the left with zeros to match the width of the passed string of zeros. e.g. Setting:
     * urlSchemeZeroPadding : { '{x}' : '0000'}
     * will cause an 'x' value of 12 to return the string '0012' for {x} in the generated URL.
     * It has the following keywords:
     * <ul>
     *  <li> <code>{z}</code>: The zero padding for the level of the tile in the tiling scheme.</li>
     *  <li> <code>{x}</code>: The zero padding for the tile X coordinate in the tiling scheme.</li>
     *  <li> <code>{y}</code>: The zero padding for the the tile Y coordinate in the tiling scheme.</li>
     *  <li> <code>{reverseX}</code>: The zero padding for the tile reverseX coordinate in the tiling scheme.</li>
     *  <li> <code>{reverseY}</code>: The zero padding for the tile reverseY coordinate in the tiling scheme.</li>
     *  <li> <code>{reverseZ}</code>: The zero padding for the reverseZ coordinate of the tile in the tiling scheme.</li>
     * </ul>
     * @memberof VTileImageryProvider.prototype
     * @type {Object}
     * @readonly
     */
    urlSchemeZeroPadding: {
      get: function () {
        return this._urlSchemeZeroPadding;
      }
    },

    /**
     * Gets the URL template to use to use to pick features.  If this property is not specified,
     * {@link VTileImageryProvider#pickFeatures} will immediately returned undefined, indicating no
     * features picked.  The URL template supports all of the keywords supported by the
     * {@link VTileImageryProvider#url} property, plus the following:
     * <ul>
     *     <li><code>{i}</code>: The pixel column (horizontal coordinate) of the picked position, where the Westernmost pixel is 0.</li>
     *     <li><code>{j}</code>: The pixel row (vertical coordinate) of the picked position, where the Northernmost pixel is 0.</li>
     *     <li><code>{reverseI}</code>: The pixel column (horizontal coordinate) of the picked position, where the Easternmost pixel is 0.</li>
     *     <li><code>{reverseJ}</code>: The pixel row (vertical coordinate) of the picked position, where the Southernmost pixel is 0.</li>
     *     <li><code>{longitudeDegrees}</code>: The longitude of the picked position in degrees.</li>
     *     <li><code>{latitudeDegrees}</code>: The latitude of the picked position in degrees.</li>
     *     <li><code>{longitudeProjected}</code>: The longitude of the picked position in the projected coordinates of the tiling scheme.</li>
     *     <li><code>{latitudeProjected}</code>: The latitude of the picked position in the projected coordinates of the tiling scheme.</li>
     *     <li><code>{format}</code>: The format in which to get feature information, as specified in the {@link GetFeatureInfoFormat}.</li>
     * </ul>
     * @memberof VTileImageryProvider.prototype
     * @type {String}
     * @readonly
     */
    pickFeaturesUrl: {
      get: function () {
        return this._pickFeaturesResource.url;
      }
    },

    /**
     * Gets the proxy used by this provider.
     * @memberof VTileImageryProvider.prototype
     * @type {Proxy}
     * @readonly
     * @default undefined
     */
    proxy: {
      get: function () {
        return this._resource.proxy;
      }
    },

    /**
     * Gets the width of each tile, in pixels. This function should
     * not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {Number}
     * @readonly
     * @default 256
     */
    tileWidth: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
        }
        return this._tileWidth;
      }
    },

    /**
     * Gets the height of each tile, in pixels.  This function should
     * not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {Number}
     * @readonly
     * @default 256
     */
    tileHeight: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
        }
        return this._tileHeight;
      }
    },

    /**
     * Gets the maximum level-of-detail that can be requested, or undefined if there is no limit.
     * This function should not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {Number}
     * @readonly
     * @default undefined
     */
    maximumLevel: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
        }
        return this._maximumLevel;
      }
    },

    /**
     * Gets the minimum level-of-detail that can be requested.  This function should
     * not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {Number}
     * @readonly
     * @default 0
     */
    minimumLevel: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
        }
        return this._minimumLevel;
      }
    },

    /**
     * Gets the tiling scheme used by this provider.  This function should
     * not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {TilingScheme}
     * @readonly
     * @default new WebMercatorTilingScheme()
     */
    tilingScheme: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
        }
        return this._tilingScheme;
      }
    },

    /**
     * Gets the rectangle, in radians, of the imagery provided by this instance.  This function should
     * not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {Rectangle}
     * @readonly
     * @default tilingScheme.rectangle
     */
    rectangle: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
        }
        return this._rectangle;
      }
    },

    /**
     * Gets the tile discard policy.  If not undefined, the discard policy is responsible
     * for filtering out "missing" tiles via its shouldDiscardImage function.  If this function
     * returns undefined, no tiles are filtered.  This function should
     * not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {TileDiscardPolicy}
     * @readonly
     * @default undefined
     */
    tileDiscardPolicy: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
        }
        return this._tileDiscardPolicy;
      }
    },

    /**
     * Gets an event that is raised when the imagery provider encounters an asynchronous error.  By subscribing
     * to the event, you will be notified of the error and can potentially recover from it.  Event listeners
     * are passed an instance of {@link TileProviderError}.
     * @memberof VTileImageryProvider.prototype
     * @type {Event}
     * @readonly
     */
    errorEvent: {
      get: function () {
        return this._errorEvent;
      }
    },

    /**
     * Gets a value indicating whether or not the provider is ready for use.
     * @memberof VTileImageryProvider.prototype
     * @type {Boolean}
     * @readonly
     */
    ready: {
      get: function () {
        return defined(this._resource);
      }
    },

    /**
     * Gets a promise that resolves to true when the provider is ready for use.
     * @memberof VTileImageryProvider.prototype
     * @type {Promise.<Boolean>}
     * @readonly
     */
    readyPromise: {
      get: function () {
        return this._readyPromise;
      }
    },

    /**
     * Gets the credit to display when this imagery provider is active.  Typically this is used to credit
     * the source of the imagery.  This function should not be called before {@link VTileImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {Credit}
     * @readonly
     * @default undefined
     */
    credit: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('credit must not be called before the imagery provider is ready.');
        }
        return this._credit;
      }
    },

    /**
     * Gets a value indicating whether or not the images provided by this imagery provider
     * include an alpha channel.  If this property is false, an alpha channel, if present, will
     * be ignored.  If this property is true, any images without an alpha channel will be treated
     * as if their alpha is 1.0 everywhere.  When this property is false, memory usage
     * and texture upload time are reduced.  This function should
     * not be called before {@link ImageryProvider#ready} returns true.
     * @memberof VTileImageryProvider.prototype
     * @type {Boolean}
     * @readonly
     * @default true
     */
    hasAlphaChannel: {
      get: function () {
        if (!this.ready) {
          throw new DeveloperError('hasAlphaChannel must not be called before the imagery provider is ready.');
        }
        return this._hasAlphaChannel;
      }
    },

    baseUrl: {
      get: function () {
        return this._baseUrl;
      }

    },

    isLabel: {
      get: function () {
        return this._isLabel;
      }
    },


    mapUrl: {
      get: function () {
        return this._mapUrl;
      }
    },

    labelUrl: {
      get: function () {
        return this._labelUrl;
      }
    },

    legendUrl: {
      get: function () {
        return this._legendUrl;
      }
    },

    pickUrl: {
      get: function () {
        return this._pickUrl;
      }
    },

    styleId: {
      get: function () {
        return this._styleId;
      }
    },

    serverName: {
      get: function () {
        return this._serverName;
      }
    },

    control: {
      get: function () {
        return this._control;
      }
    },

    labelProvider: {
      get: function () {
        return this._labelProvider;
      }
    },

    hightlightProvider: {
      get: function () {
        return this._hightlightProvider;
      }
    }
  });


  /**
   * Reinitializes this instance.  Reinitializing an instance already in use is supported, but it is not
   * recommended because existing tiles provided by the imagery provider will not be updated.
   *
   * @param {Promise.<Object>|Object} options Any of the options that may be passed to the {@link VTileImageryProvider} constructor.
   */
  VTileImageryProvider.prototype.reinitialize = function (options) {
    var that = this;
    that._readyPromise = when(options).then(function (properties) {
      if (!defined(properties)) {
        throw new DeveloperError('options is required.');
      }
      if (!defined(properties.url)) {
        throw new DeveloperError('options.url is required.');
      }

      if (!defined(options.styleId)) {
        throw new DeveloperError('options.styleId is required.');
      }


      that._baseUrl = properties.url;
      that._tileWidth = defaultValue(properties.tileWidth, 512);
      that._tileHeight = defaultValue(properties.tileHeight, 512);
      that._serverName = getSeverName(that._baseUrl);
      that._serverHost = getPrefix(that._baseUrl);
      that._styleId = properties.styleId;

      if (options.control) {
        if (typeof options.control == 'string') {
          that._control = JSON.parse(options.control);
        } else {
          that._control = options.control;
        }
      }

      if (that._control) {
        that._mapUrl = that._baseUrl + '?x={x}&y={y}&l={z}&styleId={styleId}&tilesize={width}&control=' + JSON.stringify(that._control);
      } else {
        that._mapUrl = that._baseUrl + '?x={x}&y={y}&l={z}&styleId={styleId}&tilesize={width}';
      }

      that._labelUrl = that._serverHost + '/label/' + that._serverName + '/getMap'; //后端注记地址
      that._legendUrl = that._serverHost + '/styleInfo/' + that._serverName + '/{styleId}/{level}/legend.json'; //图例地址
      that._pickUrl = that._serverHost + '/' + that._serverName + '/{styleId}/pickup?level={z}&lon={x}&lat={y}'; //属性拾取地址

      if (that._baseUrl.indexOf('mapserver/label') != -1) { //注记地址
        that._labelUrl = null;
        that._legendUrl = null;
        that._pickUrl = null;
        that._isLabel = true;
        that.enablePickFeatures = false;
      }

      var customTags = properties.customTags;
      var allTags = combine(tags, customTags);
      var allPickFeaturesTags = combine(pickFeaturesTags, customTags);
      var resource = Resource.createIfNeeded(that._mapUrl);
      var pickFeaturesResource = Resource.createIfNeeded(that._pickUrl);

      that.enablePickFeatures = defaultValue(properties.enablePickFeatures, that.enablePickFeatures);
      that.enableRequestLabel = defaultValue(properties.enableRequestLabel, that.enableRequestLabel);
      that._urlSchemeZeroPadding = defaultValue(properties.urlSchemeZeroPadding, that.urlSchemeZeroPadding);
      that._tileDiscardPolicy = properties.tileDiscardPolicy;
      that._getFeatureInfoFormats = properties.getFeatureInfoFormats;

      that._subdomains = properties.subdomains;
      if (isArray(that._subdomains)) {
        that._subdomains = that._subdomains.slice();
      } else if (defined(that._subdomains) && that._subdomains.length > 0) {
        that._subdomains = that._subdomains.split('');
      } else {
        that._subdomains = ['a', 'b', 'c'];
      }

      that._minimumLevel = defaultValue(properties.minimumLevel, 0);
      that._maximumLevel = properties.maximumLevel;
      that._tilingScheme = defaultValue(properties.tilingScheme, new GeographicTilingScheme({ellipsoid: properties.ellipsoid}));
      that._rectangle = defaultValue(properties.rectangle, that._tilingScheme.rectangle);
      that._rectangle = Rectangle.intersection(that._rectangle, that._tilingScheme.rectangle);
      that._hasAlphaChannel = defaultValue(properties.hasAlphaChannel, true);

      var credit = properties.credit;
      if (typeof credit === 'string') {
        credit = new Credit(credit);
      }
      that._credit = credit;
      that._resource = resource;
      that._tags = allTags;
      that._pickFeaturesResource = pickFeaturesResource;
      that._pickFeaturesTags = allPickFeaturesTags;

      if (that.enableRequestLabel) {
        that._labelProvider = new Cesium.VTileImageryProvider({
          url: that._labelUrl,
          styleId: that._styleId
          // control: that._control, //label的control如何获取 需要替换图层
        });
      }
      return true;
    });
  };

  /**
   * Gets the credits to be displayed when a given tile is displayed.
   *
   * @param {Number} x The tile X coordinate.
   * @param {Number} y The tile Y coordinate.
   * @param {Number} level The tile level;
   * @returns {Credit[]} The credits to be displayed when the tile is displayed.
   *
   * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
   */
  VTileImageryProvider.prototype.getTileCredits = function (x, y, level) {
    return undefined;
  };

  /**
   * Requests the image for a given tile.  This function should
   * not be called before {@link VTileImageryProvider#ready} returns true.
   *
   * @param {Number} x The tile X coordinate.
   * @param {Number} y The tile Y coordinate.
   * @param {Number} level The tile level.
   * @param {Request} [request] The request object. Intended for internal use only.
   * @returns {Promise.<Image|Canvas>|undefined} A promise for the image that will resolve when the image is available, or
   *          undefined if there are too many active requests to the server, and the request
   *          should be retried later.  The resolved image may be either an
   *          Image or a Canvas DOM object.
   */
  VTileImageryProvider.prototype.requestImage = function (x, y, level, request) {
    if (!this.ready) {
      throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
    }
    level += 2;
    return ImageryProvider.loadImage(this, buildImageResource(this, x, y, level, request));
  };

  /**
   * Asynchronously determines what features, if any, are located at a given longitude and latitude within
   * a tile.  This function should not be called before {@link ImageryProvider#ready} returns true.
   *
   * @param {Number} x The tile X coordinate.
   * @param {Number} y The tile Y coordinate.
   * @param {Number} level The tile level.
   * @param {Number} longitude The longitude at which to pick features.
   * @param {Number} latitude  The latitude at which to pick features.
   * @return {Promise.<ImageryLayerFeatureInfo[]>|undefined} A promise for the picked features that will resolve when the asynchronous
   *                   picking completes.  The resolved value is an array of {@link ImageryLayerFeatureInfo}
   *                   instances.  The array may be empty if no features are found at the given location.
   *                   It may also be undefined if picking is not supported.
   */
  VTileImageryProvider.prototype.pickFeatures = function (x, y, level, longitude, latitude) {
    if (!this.ready) {
      throw new DeveloperError('pickFeatures must not be called before the imagery provider is ready.');
    }

    if (!this.enablePickFeatures || !defined(this._pickFeaturesResource)) {
      return undefined;
    }

    var that = this;
    longitude = Math.toDegrees(longitude);
    latitude = Math.toDegrees(latitude);

    function doRequest() {
      var resource = buildPickFeaturesResource(that, x, y, level, longitude, latitude);
      return resource.fetch({
        responseType: 'json'
      }).otherwise(doRequest);
    }

    return doRequest();
  };

  VTileImageryProvider.prototype.renderLegend = function (level) {
    if (!this.ready) {
      throw new DeveloperError('renderLegend must not be called before the imagery provider is ready.');
    }

    var url = this._legendUrl.replace('{level}', level);

    return fetch(url);
  };

  VTileImageryProvider.prototype.setFilter = function (filter) {
    this._control = null;
    if (!this.url || !filter || (filter.layers.length == 0 && filter.order.length == 0)) {
      return;
    }
    for (let i = 0; i < filter.layers.length; i++) {
      let filterLayer = filter.layers[i];
      if (!filterLayer.id) {
        filter.layers.splice(i, 1);
      }
    }
    this._control = filter;

    this._resource.url = this._mapUrl + '&control=' + JSON.stringify(this._control);
    // this._resource.setQueryParameters({
    //   control:this._control
    // })
  };

  VTileImageryProvider.prototype.clearFilter = function (filter) {
    this._control = null;
    this._resource.url = this._mapUrl;
  };


  /**
   * 高亮
   * @param filter 条件
   */
  VTileImageryProvider.prototype.hightlight = function (control) {
    return new Cesium.VTileImageryProvider({
      url: this._baseUrl,
      styleId: this._styleId,
      control: JSON.stringify(control)
    });
  };

  VTileImageryProvider.prototype.getLayerMeta = function () {
    let url = this._serverHost + "/serverInfo/" + this._serverName + ".json";

    function doRequest() {
      var resource = Resource.createIfNeeded(url);
      return resource.fetch({
        responseType: 'json'
      }).otherwise(doRequest);
    }

    return doRequest();
  };

  VTileImageryProvider.prototype.getLayerList = function () {
    let that = this;
    let url = this._serverHost + "/styleInfo/" + this._serverName + "/{styleId}/layer.json";

    function doRequest() {
      var resource = Resource.createIfNeeded(url);
      var templateValues = {};
      var match = url.match(templateRegex);
      if (defined(match)) {
        match.forEach(function (tag) {
          var key = tag.substring(1, tag.length - 1); //strip {}
          if (defined(that._tags[key])) {
            templateValues[key] = that._tags[key](that);
          }
        });
      }
      resource = resource.getDerivedResource({
        templateValues: templateValues
      });

      return resource.fetch({
        responseType: 'json'
      }).otherwise(doRequest);
    }

    return doRequest();
  };


  /**
   * 获取主键key
   * @param re
   * @returns {string}
   */
  VTileImageryProvider.prototype.getPrimarykey = function (re) {
    var primaryKey;
    for (var key in re) {
      var value = re[key];
      for (var kk in value) {
        var vv = value[kk];
        for (var kkk in vv) {
          var vvv = vv[kkk];
          if (vvv == kk) {
            primaryKey = kkk;
            break;
          }
        }
        if (primaryKey) {
          break;
        }
      }
    }
    return primaryKey;
  };

  /**
   * 根据key获取值
   * @param re
   */
  VTileImageryProvider.prototype.getValuesByKey = function (re, k) {
    var values = [];
    for (var key in re) {
      var value = re[key];
      for (var kk in value) {
        var vv = value[kk];
        if (vv && vv.hasOwnProperty(k)) {
          var value = vv[k];
          if (typeof value != 'undefined' && value != null && value != '') {
            values.push(value);
          }
        }
      }
    }
    return values;
  };


  var degreesScratchComputed = false;
  var degreesScratch = new Rectangle();
  var projectedScratchComputed = false;
  var projectedScratch = new Rectangle();

  function buildImageResource(imageryProvider, x, y, level, request) {
    degreesScratchComputed = false;
    projectedScratchComputed = false;

    var resource = imageryProvider._resource;
    var url = resource.getUrlComponent(true); //TODO
    var allTags = imageryProvider._tags;
    var templateValues = {};
    var match = url.match(templateRegex);
    if (defined(match)) {
      match.forEach(function (tag) {
        var key = tag.substring(1, tag.length - 1); //strip {}
        if (defined(allTags[key])) {
          templateValues[key] = allTags[key](imageryProvider, x, y, level);
        }
      });
    }

    return resource.getDerivedResource({
      request: request,
      templateValues: templateValues
    });
  }

  var ijScratchComputed = false;
  var ijScratch = new Cartesian2();
  var longitudeLatitudeProjectedScratchComputed = false;

  function buildPickFeaturesResource(imageryProvider, x, y, level, longitude, latitude) {
    degreesScratchComputed = false;
    projectedScratchComputed = false;
    ijScratchComputed = false;
    longitudeLatitudeProjectedScratchComputed = false;

    var resource = imageryProvider._pickFeaturesResource;
    var url = resource.getUrlComponent(true);
    var allTags = imageryProvider._pickFeaturesTags;
    var templateValues = {};
    var match = url.match(templateRegex);
    if (defined(match)) {
      match.forEach(function (tag) {
        var key = tag.substring(1, tag.length - 1); //strip {}
        if (defined(allTags[key])) {
          templateValues[key] = allTags[key](imageryProvider, longitude, latitude, level);
        }
      });
    }

    return resource.getDerivedResource({
      templateValues: templateValues
    });
  }

  function padWithZerosIfNecessary(imageryProvider, key, value) {
    if (imageryProvider &&
      imageryProvider.urlSchemeZeroPadding &&
      imageryProvider.urlSchemeZeroPadding.hasOwnProperty(key)) {
      var paddingTemplate = imageryProvider.urlSchemeZeroPadding[key];
      if (typeof paddingTemplate === 'string') {
        var paddingTemplateWidth = paddingTemplate.length;
        if (paddingTemplateWidth > 1) {
          value = (value.length >= paddingTemplateWidth) ? value : new Array(paddingTemplateWidth - value.toString().length + 1).join('0') + value;
        }
      }
    }
    return value;
  }

  function getSeverName(url) {
    var startInd = url.indexOf('vmap/');
    var endInd = url.indexOf('/getMAP');
    var serverName = null;
    if (startInd > 0 && endInd > 0) {
      serverName = url.substring(startInd + 5, endInd);
    }
    return serverName;
  }

  function getPrefix(url) {
    var index = url.indexOf('mapserver');
    var prefix = null;
    if (index > 0) {
      prefix = url.substring(0, index + 9);
    }
    return prefix;
  }


  function styleIdTag(imageryProvider, x, y, level, styleId) {
    if (!styleId) {
      styleId = imageryProvider._styleId;
    }
    return padWithZerosIfNecessary(imageryProvider, '{styleId}', styleId);
  }

  function xTag(imageryProvider, x, y, level) {
    return padWithZerosIfNecessary(imageryProvider, '{x}', x);
  }

  function reverseXTag(imageryProvider, x, y, level) {
    var reverseX = imageryProvider.tilingScheme.getNumberOfXTilesAtLevel(level) - x - 1;
    return padWithZerosIfNecessary(imageryProvider, '{reverseX}', reverseX);
  }

  function yTag(imageryProvider, x, y, level) {
    return padWithZerosIfNecessary(imageryProvider, '{y}', y);
  }

  function reverseYTag(imageryProvider, x, y, level) {
    var reverseY = imageryProvider.tilingScheme.getNumberOfYTilesAtLevel(level) - y - 1;
    return padWithZerosIfNecessary(imageryProvider, '{reverseY}', reverseY);
  }

  function reverseZTag(imageryProvider, x, y, level) {
    var maximumLevel = imageryProvider.maximumLevel;
    var reverseZ = defined(maximumLevel) && level < maximumLevel ? maximumLevel - level - 1 : level;
    return padWithZerosIfNecessary(imageryProvider, '{reverseZ}', reverseZ);
  }

  function zTag(imageryProvider, x, y, level) {
    return padWithZerosIfNecessary(imageryProvider, '{z}', level);
  }

  function sTag(imageryProvider, x, y, level) {
    var index = (x + y + level) % imageryProvider._subdomains.length;
    return imageryProvider._subdomains[index];
  }

  function computeDegrees(imageryProvider, x, y, level) {
    if (degreesScratchComputed) {
      return;
    }

    imageryProvider.tilingScheme.tileXYToRectangle(x, y, level, degreesScratch);
    degreesScratch.west = CesiumMath.toDegrees(degreesScratch.west);
    degreesScratch.south = CesiumMath.toDegrees(degreesScratch.south);
    degreesScratch.east = CesiumMath.toDegrees(degreesScratch.east);
    degreesScratch.north = CesiumMath.toDegrees(degreesScratch.north);

    degreesScratchComputed = true;
  }

  function westDegreesTag(imageryProvider, x, y, level) {
    computeDegrees(imageryProvider, x, y, level);
    return degreesScratch.west;
  }

  function southDegreesTag(imageryProvider, x, y, level) {
    computeDegrees(imageryProvider, x, y, level);
    return degreesScratch.south;
  }

  function eastDegreesTag(imageryProvider, x, y, level) {
    computeDegrees(imageryProvider, x, y, level);
    return degreesScratch.east;
  }

  function northDegreesTag(imageryProvider, x, y, level) {
    computeDegrees(imageryProvider, x, y, level);
    return degreesScratch.north;
  }

  function computeProjected(imageryProvider, x, y, level) {
    if (projectedScratchComputed) {
      return;
    }

    imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, projectedScratch);

    projectedScratchComputed = true;
  }

  function westProjectedTag(imageryProvider, x, y, level) {
    computeProjected(imageryProvider, x, y, level);
    return projectedScratch.west;
  }

  function southProjectedTag(imageryProvider, x, y, level) {
    computeProjected(imageryProvider, x, y, level);
    return projectedScratch.south;
  }

  function eastProjectedTag(imageryProvider, x, y, level) {
    computeProjected(imageryProvider, x, y, level);
    return projectedScratch.east;
  }

  function northProjectedTag(imageryProvider, x, y, level) {
    computeProjected(imageryProvider, x, y, level);
    return projectedScratch.north;
  }

  function widthTag(imageryProvider, x, y, level) {
    return imageryProvider.tileWidth;
  }

  function heightTag(imageryProvider, x, y, level) {
    return imageryProvider.tileHeight;
  }

  function iTag(imageryProvider, x, y, level, longitude, latitude, format) {
    computeIJ(imageryProvider, x, y, level, longitude, latitude);
    return ijScratch.x;
  }

  function jTag(imageryProvider, x, y, level, longitude, latitude, format) {
    computeIJ(imageryProvider, x, y, level, longitude, latitude);
    return ijScratch.y;
  }

  function reverseITag(imageryProvider, x, y, level, longitude, latitude, format) {
    computeIJ(imageryProvider, x, y, level, longitude, latitude);
    return imageryProvider.tileWidth - ijScratch.x - 1;
  }

  function reverseJTag(imageryProvider, x, y, level, longitude, latitude, format) {
    computeIJ(imageryProvider, x, y, level, longitude, latitude);
    return imageryProvider.tileHeight - ijScratch.y - 1;
  }

  var rectangleScratch = new Rectangle();
  var longitudeLatitudeProjectedScratch = new Cartesian3();

  function computeIJ(imageryProvider, x, y, level, longitude, latitude, format) {
    if (ijScratchComputed) {
      return;
    }

    computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
    var projected = longitudeLatitudeProjectedScratch;

    var rectangle = imageryProvider.tilingScheme.tileXYToNativeRectangle(x, y, level, rectangleScratch);
    ijScratch.x = (imageryProvider.tileWidth * (projected.x - rectangle.west) / rectangle.width) | 0;
    ijScratch.y = (imageryProvider.tileHeight * (rectangle.north - projected.y) / rectangle.height) | 0;
    ijScratchComputed = true;
  }

  function longitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
    return CesiumMath.toDegrees(longitude);
  }

  function latitudeDegreesTag(imageryProvider, x, y, level, longitude, latitude, format) {
    return CesiumMath.toDegrees(latitude);
  }

  function longitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
    computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
    return longitudeLatitudeProjectedScratch.x;
  }

  function latitudeProjectedTag(imageryProvider, x, y, level, longitude, latitude, format) {
    computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude);
    return longitudeLatitudeProjectedScratch.y;
  }

  var cartographicScratch = new Cartographic();

  function computeLongitudeLatitudeProjected(imageryProvider, x, y, level, longitude, latitude, format) {
    if (longitudeLatitudeProjectedScratchComputed) {
      return;
    }

    if (imageryProvider.tilingScheme.projection instanceof GeographicProjection) {
      longitudeLatitudeProjectedScratch.x = CesiumMath.toDegrees(longitude);
      longitudeLatitudeProjectedScratch.y = CesiumMath.toDegrees(latitude);
    } else {
      var cartographic = cartographicScratch;
      cartographic.longitude = longitude;
      cartographic.latitude = latitude;
      imageryProvider.tilingScheme.projection.project(cartographic, longitudeLatitudeProjectedScratch);
    }

    longitudeLatitudeProjectedScratchComputed = true;
  }

  function formatTag(imageryProvider, x, y, level, longitude, latitude, format) {
    return format;
  }

  Cesium.VTileImageryProvider = VTileImageryProvider;


  Cesium.buildImageResource = buildImageResource; //TODO;

})(Cesium.Cartesian2,
  Cesium.Cartesian3,
  Cesium.Cartographic,
  Cesium.combine,
  Cesium.Credit,
  Cesium.defaultValue,
  Cesium.defined,
  Cesium.defineProperties ||  Object.defineProperties,
  Cesium.DeveloperError,
  Cesium.Event,
  Cesium.GeographicProjection,
  Cesium.isArray,
  Cesium.CesiumMath,
  Cesium.Rectangle,
  Cesium.Resource,
  Cesium.WebMercatorTilingScheme,
  Cesium.GeographicTilingScheme,
  Cesium.when,
  Cesium.ImageryProvider,
  Cesium.Math);
